package vistop.ui;

import java.util.ArrayList;
import java.util.List;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GL2ES1;
import javax.media.opengl.GL2GL3;
import javax.media.opengl.GLContext;
import javax.media.opengl.GLDrawableFactory;
import javax.media.opengl.GLProfile;
import javax.media.opengl.fixedfunc.GLMatrixFunc;
import javax.media.opengl.glu.GLU;
import javax.media.opengl.glu.GLUtessellator;
import javax.media.opengl.glu.GLUtessellatorCallback;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.opengl.GLCanvas;
import org.eclipse.swt.opengl.GLData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;

import vistop.Main;
import vistop.datatypes.Polygon;
import vistop.datatypes.Vertex;

public class JOGLView extends ViewPart {

	/** Holds the OpenGL canvas. */
	private Composite composite;

	/** Widget that displays OpenGL content. */
	private GLCanvas glcanvas;

	/** Used to get OpenGL object that we need to access OpenGL functions. */
	private GLContext glcontext;

	/** Master rotation angle in degrees. */
	private int rot = 0;

	//private List<List<Vertex>> polygonCompVertex;
	private List<Polygon> polygons;

	public JOGLView() {
		//Main m = new Main();
		//this.polygons = m.polygons;
		this.polygons = new ArrayList<Polygon>();
		polygons.add(new Polygon(0, Polygon.getTestPolygon()));
		polygons.add(new Polygon(0, Polygon.getTestPolygon3()));
		
	}
	
	

	@Override
	public void createPartControl( Composite compositeParent ) {
		GLProfile glprofile = GLProfile.get(GLProfile.GL2);

		composite = new Composite( compositeParent, SWT.NONE );
		composite.setLayout( new FillLayout() );

		GLData gldata = new GLData();
		gldata.doubleBuffer = true;
		glcanvas = new GLCanvas( composite, SWT.NO_BACKGROUND, gldata );
		glcanvas.setCurrent();
		glcontext = GLDrawableFactory.getFactory( glprofile ).createExternalGLContext();

		glcanvas.addListener(SWT.Resize, new Listener() {
			public void handleEvent(Event event) {
				Rectangle bounds = glcanvas.getBounds();
				float fAspect = (float) bounds.width / (float) bounds.height;
				glcanvas.setCurrent();
				glcontext.makeCurrent();
				GL2 gl = glcontext.getGL().getGL2();
				gl.glViewport(0, 0, bounds.width, bounds.height);
				gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
				gl.glLoadIdentity();
				GLU glu = new GLU();
				glu.gluPerspective(50.0f, fAspect, 0.5f, 400.0f);
				gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
				gl.glLoadIdentity();
				glcontext.release();
			}
		});

		glcontext.makeCurrent();
		GL2 gl = glcontext.getGL().getGL2();
		gl.setSwapInterval(1);
		gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
		gl.glColor3f(1.0f, 0.0f, 0.0f);
		gl.glHint(GL2ES1.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
		gl.glClearDepth(1.0);
		gl.glLineWidth(2);
		gl.glEnable(GL.GL_DEPTH_TEST);
		glcontext.release();

		(new Thread() {
			public void run() {
				while( (glcanvas != null) && !glcanvas.isDisposed() ) {
					render();
					try {
						// don't make loop too tight, or not enough time
						// to process window messages properly
						sleep( 1 );
					} catch( InterruptedException interruptedexception ) {
						// we just quit on interrupt, so nothing required here
					}
				}
			}
		}).start();
	}

	protected void render() {
		PlatformUI.getWorkbench().getDisplay().syncExec( new Runnable() {
			public void run() {
				if( (glcanvas != null) && !glcanvas.isDisposed()) {
					
					glcanvas.setCurrent();
					glcontext.makeCurrent();
					GL2 gl = glcontext.getGL().getGL2();
					gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
					gl.glClearColor(.3f, .5f, .8f, 1.0f);
					gl.glLoadIdentity();
					gl.glTranslatef(0, 0, -20.0f);
					//gl.glRotatef(0.3f * rot, 0, 0, 1);
					//rot++;
					gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2GL3.GL_FILL);
					gl.glCullFace(GL.GL_CULL_FACE);
					gl.glColor3f(0.9f, 0.9f, 0.9f);

					GLU glu = new GLU();
					MyTessCallBack tessCallBack = new MyTessCallBack(gl, glu);
					//Polygon p = polygons.get(0);
					//p.printComponents(p.getPolygonComponentsVertex());
					for (Polygon p : polygons) {
						drawTessPolygon(gl, tessCallBack, p.getPolygonComponentsVertex());
					}

					glcanvas.swapBuffers();
					glcontext.release();
				}
			}
		});
	}

	protected void drawTessPolygon(GL2 gl, MyTessCallBack tcb, 
			List<List<Vertex>> polygonComponents) {
		GLUtessellator tobj = GLU.gluNewTess();

		GLU.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, tcb);// glVertex3dv);
		GLU.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, tcb);// beginCallback);
		GLU.gluTessCallback(tobj, GLU.GLU_TESS_END, tcb);// endCallback);
		GLU.gluTessCallback(tobj, GLU.GLU_TESS_ERROR, tcb);// errorCallback);
		
		gl.glShadeModel(GL2.GL_SMOOTH);
		GLU.gluTessBeginPolygon(tobj, null);
		for (List<Vertex> contour : polygonComponents) {
			GLU.gluTessBeginContour(tobj);
			for(Vertex v : contour) {
				GLU.gluTessVertex(tobj, v.getCoord(), 0, v.getCoord());
			}
			GLU.gluTessEndContour(tobj);
		}
		GLU.gluTessEndPolygon(tobj);
		gl.glEndList();
	}
	
	@Override
	public void setFocus() {
	}

	@Override
	public void dispose() {
		
		super.dispose();
		glcanvas.dispose();
	}

	public class MyTessCallBack implements GLUtessellatorCallback {
		private GL2 gl;
		private GLU glu;

		public MyTessCallBack(GL2 gl, GLU glu) {
			this.gl = gl;
			this.glu = glu;
		}

		public void begin(int type) {
			gl.glBegin(type);
		}

		public void end() {
			gl.glEnd();
		}

		public void vertex(Object vertexData) {
			double[] pointer;
			if (vertexData instanceof double[]) {
				pointer = (double[]) vertexData;
				if (pointer.length == 6) gl.glColor3dv(pointer, 3);
				gl.glVertex3dv(pointer, 0);
			}
		}

		public void vertexData(Object vertexData, Object polygonData) {
		}

		/*
		 * combineCallback is used to create a new vertex when edges intersect.
		 * coordinate location is trivial to calculate, but weight[4] may be used to
		 * average color, normal, or texture coordinate data. In this program, color
		 * is weighted.
		 */
		public void combine(double[] coords, Object[] data, //
				float[] weight, Object[] outData) {
			double[] vertex = new double[6];
			int i;

			vertex[0] = coords[0];
			vertex[1] = coords[1];
			vertex[2] = coords[2];
			for (i = 3; i < 6/* 7OutOfBounds from C! */; i++)
				vertex[i] = weight[0] //
						* ((double[]) data[0])[i] + weight[1]
								* ((double[]) data[1])[i] + weight[2]
										* ((double[]) data[2])[i] + weight[3]
												* ((double[]) data[3])[i];
			outData[0] = vertex;
		}

		public void combineData(double[] coords, Object[] data, 
				float[] weight, Object[] outData, Object polygonData) {
		}

		public void error(int errnum) {
			String estring;

			estring = glu.gluErrorString(errnum);
			System.err.println("Tessellation Error: " + estring);
			System.exit(0);
		}

		public void beginData(int type, Object polygonData) {
		}

		public void endData(Object polygonData) {
		}

		public void edgeFlag(boolean boundaryEdge) {
		}

		public void edgeFlagData(boolean boundaryEdge, Object polygonData) {
		}

		public void errorData(int errnum, Object polygonData) {
		}
	}

}